/*
 *  Copyright 2005 Blandware (http://www.blandware.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.blandware.atleap.service.forum.impl;

import com.blandware.atleap.common.util.PartialCollection;
import com.blandware.atleap.common.util.QueryInfo;
import com.blandware.atleap.common.util.StringUtil;
import com.blandware.atleap.model.core.ContentLocale;
import com.blandware.atleap.model.core.User;
import com.blandware.atleap.model.forum.Forum;
import com.blandware.atleap.model.forum.Topic;
import com.blandware.atleap.model.forum.Post;
import com.blandware.atleap.model.forum.ForumSubscription;
import com.blandware.atleap.persistence.core.ContentLocaleDAO;
import com.blandware.atleap.persistence.core.UserDAO;
import com.blandware.atleap.persistence.forum.ForumDAO;
import com.blandware.atleap.persistence.forum.TopicDAO;
import com.blandware.atleap.persistence.forum.PostDAO;
import com.blandware.atleap.persistence.forum.ForumSubscriptionDAO;
import com.blandware.atleap.persistence.exception.DeleteException;
import com.blandware.atleap.persistence.exception.UpdateException;
import com.blandware.atleap.service.core.impl.BaseManagerImpl;
import com.blandware.atleap.service.exception.BeanAlreadyExistsException;
import com.blandware.atleap.service.exception.BeanNotFoundException;
import com.blandware.atleap.service.exception.OwnerNotFoundException;
import com.blandware.atleap.service.forum.ForumManager;

import java.util.*;

/**
 * <p>Implementation of ForumManager</p>
 * <p><a href="ForumManagerImpl.java.html"><i>View source</i></a></p>
 *
 * @author Roman Puchkovskiy <a href="mailto:roman.puchkovskiy@blandware.com">
 *         &lt;roman.puchkovskiy@blandware.com&gt;</a>
 * @version $Revision: 1.6 $ $Date: 2008/01/13 17:20:04 $
 */
public class ForumManagerImpl extends BaseManagerImpl implements ForumManager {

    /**
     * DAO to work with forums
     */
    ForumDAO forumDAO;

    /**
     * DAO to work with content locales
     */
    ContentLocaleDAO contentLocaleDAO;

    /**
     * DAO to work with topics
     */
    TopicDAO topicDAO;

    /**
     * DAO to work with posts
     */
    PostDAO postDAO;

    /**
     * DAO to work with forum subscriptions
     */
    ForumSubscriptionDAO forumSubscriptionDAO;

    /**
     * DAO to work with users
     */
    UserDAO userDAO;

    /**
     * Length of token used to secure operations with forum subscriptions
     */
    protected static final int TOKEN_LEN = 10;

    /**
     * Sets DAO to use when working with forums.
     *
     * @param forumDAO DAO to set
     */
    public void setForumDAO(ForumDAO forumDAO) {
        this.forumDAO = forumDAO;
    }

    /**
     * Sets DAO to use when working with content locales.
     *
     * @param contentLocaleDAO DAO to set
     */
    public void setContentLocaleDAO(ContentLocaleDAO contentLocaleDAO) {
        this.contentLocaleDAO = contentLocaleDAO;
    }

    /**
     * Sets DAO to use when working with topics.
     *
     * @param topicDAO DAO to set
     */
    public void setTopicDAO(TopicDAO topicDAO) {
        this.topicDAO = topicDAO;
    }

    /**
     * Sets DAO to use when working with posts.
     *
     * @param postDAO DAO to set
     */
    public void setPostDAO(PostDAO postDAO) {
        this.postDAO = postDAO;
    }

    /**
     * Sets DAO to work with forum subscriptions.
     *
     * @param forumSubscriptionDAO DAO to set
     */
    public void setForumSubscriptionDAO(ForumSubscriptionDAO forumSubscriptionDAO) {
        this.forumSubscriptionDAO = forumSubscriptionDAO;
    }

    /**
     * Sets DAO to work with users.
     *
     * @param userDAO DAO to set
     */
    public void setUserDAO(UserDAO userDAO) {
        this.userDAO = userDAO;
    }

    //  F O R U M S

	//~ CRUD Methods ================================================================

	/**
	 * @see com.blandware.atleap.service.forum.ForumManager#createForum(com.blandware.atleap.model.forum.Forum, Long, String, java.util.Map)
	 */
	public Long createForum(Forum forum, Long parentId,
                            String contentLocaleIdentifier, Map linkedObjects)
            throws BeanNotFoundException, BeanAlreadyExistsException {

		if ( log.isDebugEnabled() ) {
			log.debug("Creating new forum...");
		}

        boolean notRoot = parentId != null;

		Forum parentForum = null;
        if (notRoot) {
		    parentForum = forumDAO.retrieveForum(parentId);
        }

		if ( notRoot && parentForum == null ) {
			String errorMessage = "No forum with ID=" + parentId + "could be found";
			if ( log.isErrorEnabled() ) {
				log.error(errorMessage);
			}
			throw new OwnerNotFoundException(errorMessage);
		}

        ContentLocale language;
        language = contentLocaleDAO.retrieveContentLocale(contentLocaleIdentifier);
        if (language == null) {
			String errorMessage = "No content locale with identifier=" + contentLocaleIdentifier + "could be found";
			if ( log.isErrorEnabled() ) {
				log.error(errorMessage);
			}
			throw new BeanNotFoundException(errorMessage);
        }

		if ( forumDAO.hasDuplicates(forum, parentId) ) {
			throw new BeanAlreadyExistsException("Forum with the same title and parent already exists; parent ID is " + parentId);
		}

		Long forumId = forumDAO.createForum(forum, parentForum, language, linkedObjects);

		if ( log.isDebugEnabled() ) {
			log.debug("Forum has been created successfully. Its ID is " + forumId);
		}
		return forumId;
	}

	/**
	 * @see com.blandware.atleap.service.forum.ForumManager#retrieveForum(Long)
	 */
	public Forum retrieveForum(Long forumId) {
		Forum forum;
		forum = forumDAO.retrieveForum(forumId);
		return forum;
	}

	/**
	 * @see com.blandware.atleap.service.forum.ForumManager#updateForum(com.blandware.atleap.model.forum.Forum, Long, java.util.Map)
	 */
	public void updateForum(Forum forum, Long parentId, Map linkedObjects)
            throws BeanNotFoundException, BeanAlreadyExistsException {

		// remove forum from cache in order to prevent Hibernate from assigning
        // new version number
		forumDAO.removeFromCache(forum);

		if ( log.isDebugEnabled() ) {
			log.debug("Updating forum with ID=" + forum.getId() + "...");
		}

        boolean notRoot = parentId != null;

		Forum parentForum = null;
        if (notRoot) {
		    parentForum = forumDAO.retrieveForum(parentId);
        }

		if ( notRoot && parentForum == null ) {
			String errorMessage = "No forum with ID=" + parentId + "could be found";
			if ( log.isErrorEnabled() ) {
				log.error(errorMessage);
			}
			throw new OwnerNotFoundException(errorMessage);
		}

		if ( forumDAO.hasDuplicates(forum, parentId) ) {
			throw new BeanAlreadyExistsException("Forum with the same title and parent already exists; parent ID is " + parentId);
		}

		forumDAO.updateForum(forum, parentForum, linkedObjects);

		if ( log.isDebugEnabled() ) {
			log.debug("Forum was updated successfully.");
		}
	}

	/**
	 * @see com.blandware.atleap.service.forum.ForumManager#updateForum(com.blandware.atleap.model.forum.Forum)
	 */
	public void updateForum(Forum forum)
            throws BeanNotFoundException, BeanAlreadyExistsException {
        Long parentId = null;
        if (forum.getParent() != null) {
            parentId = forum.getParent().getId();
        }
        updateForum(forum, parentId, null);
    }

	/**
	 * @see com.blandware.atleap.service.forum.ForumManager#deleteForum(Long)
	 */
	public void deleteForum(Long forumId) throws BeanNotFoundException {
		if ( log.isDebugEnabled() ) {
			log.debug("Deleting forum with ID=" + forumId);
		}

		Forum forum = forumDAO.retrieveForum(forumId);
		if ( forum == null ) {
			String errorMessage = "No forum with ID=" + forumId + "could be found";
			throw new BeanNotFoundException(errorMessage);
		}

		forumDAO.deleteForum(forum);

		if ( log.isDebugEnabled() ) {
			log.debug("Forum has been deleted successfully");
		}
	}

	// ~ Additional methods ================================================================

	/**
	 * @see com.blandware.atleap.service.forum.ForumManager#listForums(com.blandware.atleap.common.util.QueryInfo)
	 */
	public PartialCollection listForums(QueryInfo queryInfo) {
		return forumDAO.listForums(queryInfo);
	}

    /**
     * @see com.blandware.atleap.service.forum.ForumManager#moveForum(com.blandware.atleap.model.forum.Forum, boolean)
     */
    public void moveForum(Forum forum, boolean up) {
        forumDAO.moveForum(forum, up);
    }

    /**
     * @see com.blandware.atleap.service.forum.ForumManager#getFirstLastForumOccupiedPositions(Long)
     */
    public Integer[] getFirstLastForumOccupiedPositions(Long parentId) {
        return forumDAO.getFirstLastOccupiedPositions(parentId);
    }

    //  T O P I C S

    // ~ CRUD methods ================================================================

    /**
     * @see com.blandware.atleap.service.forum.ForumManager#createTopic(com.blandware.atleap.model.forum.Topic, Long, com.blandware.atleap.model.forum.Post)
     */
    public Long createTopic(Topic topic, Long forumId, Post firstPost)
            throws BeanAlreadyExistsException, OwnerNotFoundException {
		if ( log.isDebugEnabled() ) {
			log.debug("Creating new topic...");
		}

		Forum forum;
        forum = forumDAO.retrieveForum(forumId);

		if ( forum == null ) {
			String errorMessage = "No forum with ID=" + forumId + "could be found";
			if ( log.isErrorEnabled() ) {
				log.error(errorMessage);
			}
			throw new OwnerNotFoundException(errorMessage);
		}

		Long topicId = topicDAO.createTopic(topic, forum, firstPost);

		if ( log.isDebugEnabled() ) {
			log.debug("Topic has been created successfully. Its ID is " + topicId);
		}
		return topicId;
    }

    /**
     * @see com.blandware.atleap.service.forum.ForumManager#retrieveTopic(Long)
     */
    public Topic retrieveTopic(Long topicId) {
        return topicDAO.retrieveTopic(topicId);
    }

    /**
     * @see com.blandware.atleap.service.forum.ForumManager#deleteTopic(Long)
     */
    public void deleteTopic(Long topicId) throws BeanNotFoundException {
		if ( log.isDebugEnabled() ) {
			log.debug("Deleting topic with ID=" + topicId);
		}

		Topic topic = topicDAO.retrieveTopic(topicId);
		if ( topic == null ) {
			String errorMessage = "No topic with ID=" + topicId + "could be found";
			throw new BeanNotFoundException(errorMessage);
		}

		topicDAO.deleteTopic(topic);

		if ( log.isDebugEnabled() ) {
			log.debug("Topic has been deleted successfully");
		}
    }

    // ~ Additional methods ================================================================

    /**
     * @see com.blandware.atleap.service.forum.ForumManager#listTopics(com.blandware.atleap.common.util.QueryInfo)
     */
    public PartialCollection listTopics(QueryInfo queryInfo) {
        return topicDAO.listTopics(queryInfo);
    }

    //  P O S T S

    // ~ CRUD methods ================================================================

    /**
     * @see com.blandware.atleap.service.forum.ForumManager#createPost(com.blandware.atleap.model.forum.Post, Long)
     */
    public Long createPost(Post post, Long topicId)
            throws BeanAlreadyExistsException, OwnerNotFoundException {
		if ( log.isDebugEnabled() ) {
			log.debug("Creating new post...");
		}

		Topic topic;
        topic = topicDAO.retrieveTopic(topicId);

		if ( topic == null ) {
			String errorMessage = "No topic with ID=" + topicId + "could be found";
			if ( log.isErrorEnabled() ) {
				log.error(errorMessage);
			}
			throw new OwnerNotFoundException(errorMessage);
		}

		Long postId = postDAO.createPost(post, topic);

		if ( log.isDebugEnabled() ) {
			log.debug("Post has been created successfully. Its ID is " + postId);
		}
		return postId;
    }

    /**
     * @see com.blandware.atleap.service.forum.ForumManager#retrievePost(Long)
     */
    public Post retrievePost(Long postId) {
        return postDAO.retrievePost(postId);
    }

    /**
     * @see com.blandware.atleap.service.forum.ForumManager#updatePost(com.blandware.atleap.model.forum.Post, Long)
     */
    public void updatePost(Post post, Long topicId) throws OwnerNotFoundException {

		// remove post from cache in order to prevent Hibernate from assigning
        // new version number
		postDAO.removeFromCache(post);

		if ( log.isDebugEnabled() ) {
			log.debug("Updating post with ID=" + post.getId() + "...");
		}

		Topic topic;
	    topic = topicDAO.retrieveTopic(topicId);

		if ( topic == null ) {
			String errorMessage = "No topic with ID=" + topicId + "could be found";
			if ( log.isErrorEnabled() ) {
				log.error(errorMessage);
			}
			throw new OwnerNotFoundException(errorMessage);
		}

        // this is done to prevent exception because of different objects with
        // same ID
        postDAO.removeFromCache(topic.getFirstPost());
        postDAO.removeFromCache(topic.getLastReply());

		postDAO.updatePost(post, topic);

		if ( log.isDebugEnabled() ) {
			log.debug("Post was updated successfully.");
		}
    }

    /**
     * @see com.blandware.atleap.service.forum.ForumManager#updatePost(com.blandware.atleap.model.forum.Post)
     */
    public void updatePost(Post post) throws OwnerNotFoundException {

		// remove post from cache in order to prevent Hibernate from assigning
        // new version number
		postDAO.removeFromCache(post);

		if ( log.isDebugEnabled() ) {
			log.debug("Updating post with ID=" + post.getId() + "...");
		}

		postDAO.updatePost(post);

		if ( log.isDebugEnabled() ) {
			log.debug("Post was updated successfully.");
		}
    }

    /**
     * @see com.blandware.atleap.service.forum.ForumManager#deletePost(Long)
     */
    public void deletePost(Long postId) throws BeanNotFoundException, DeleteException {
		if ( log.isDebugEnabled() ) {
			log.debug("Deleting post with ID=" + postId);
		}

		Post post = postDAO.retrievePost(postId);
		if ( post == null ) {
			String errorMessage = "No post with ID=" + postId + "could be found";
			throw new BeanNotFoundException(errorMessage);
		}

        if (post.getFirst().booleanValue()) {
            throw new DeleteException("Cannot delete post because it's first in its topic; ID is " + postId);
        }

		postDAO.deletePost(post);

		if ( log.isDebugEnabled() ) {
			log.debug("Post has been deleted successfully");
		}
    }

    // ~ Additional methods ================================================================

    /**
     * @see com.blandware.atleap.service.forum.ForumManager#listPosts(com.blandware.atleap.common.util.QueryInfo)
     */
    public PartialCollection listPosts(QueryInfo queryInfo) {
        return postDAO.listPosts(queryInfo);
    }

    //  F O R U M   S U B S C R I P T I O N S

    // ~ CRUD methods ================================================================

    /**
     * @see com.blandware.atleap.service.forum.ForumManager#createSubscription(com.blandware.atleap.model.forum.ForumSubscription, Long, Long, String)
     */
    public Long createSubscription(ForumSubscription forumSubscription, Long forumId, Long topicId, String userName) throws OwnerNotFoundException, BeanAlreadyExistsException {
		if ( log.isDebugEnabled() ) {
			log.debug("Creating new forum subscription...");
		}

        if (forumId == null && topicId == null) {
			String errorMessage = "Either forumId or topicId must be specified";
			if ( log.isErrorEnabled() ) {
				log.error(errorMessage);
			}
			throw new OwnerNotFoundException(errorMessage);
        }

        Forum forum = null;
		Topic topic = null;
        boolean isForum = forumId != null;

        if (isForum) {
            forum = forumDAO.retrieveForum(forumId);
            if ( forum == null ) {
                String errorMessage = "No forum with ID=" + forumId + "could be found";
                if ( log.isErrorEnabled() ) {
                    log.error(errorMessage);
                }
                throw new OwnerNotFoundException(errorMessage);
            }
        } else {
            topic = topicDAO.retrieveTopic(topicId);
            if ( topic == null ) {
                String errorMessage = "No topic with ID=" + topicId + "could be found";
                if ( log.isErrorEnabled() ) {
                    log.error(errorMessage);
                }
                throw new OwnerNotFoundException(errorMessage);
            }
        }

        User user = null;
        boolean anonymous = userName == null;
        if (anonymous) {
            forumSubscription.setActivated(Boolean.FALSE);
            forumSubscription.setToken(StringUtil.getRandomToken(TOKEN_LEN));
            forumSubscription.setDate(new Date());
        } else {
            forumSubscription.setActivated(Boolean.TRUE);
            forumSubscription.setToken(null);
            forumSubscription.setDate(null);
            forumSubscription.setEmail(null);
            user = userDAO.retrieveUser(userName);
        }

		if ( forumSubscriptionDAO.hasDuplicates(forumSubscription, forumId, topicId, userName) ) {
			throw new BeanAlreadyExistsException("Such forum subscription already exists");
		}

		Long subscriptionId = forumSubscriptionDAO.createForumSubscription(forumSubscription, forum, topic, user);

		if ( log.isDebugEnabled() ) {
			log.debug("Forum subscription has been created successfully. Its ID is " + subscriptionId);
		}
		return subscriptionId;
    }

    /**
     * @see com.blandware.atleap.service.forum.ForumManager#deleteSubscription(Long, String)
     */
    public void deleteSubscription(Long id, String token) throws
            BeanNotFoundException, DeleteException {
		if ( log.isDebugEnabled() ) {
			log.debug("Deleting forum subscription with ID=" + id);
		}

		ForumSubscription forumSubscription = forumSubscriptionDAO.retrieveForumSubscription(id);
		if ( forumSubscription == null ) {
			String errorMessage = "No forum subscription with ID=" + id + "could be found";
			throw new BeanNotFoundException(errorMessage);
		}

        if (!token.equals(forumSubscription.getToken())) {
            String errorMessage = "Forum subscription token does not match one on database; no deletion performed";
            throw new DeleteException(errorMessage);
        }

		forumSubscriptionDAO.deleteForumSubscription(forumSubscription);

		if ( log.isDebugEnabled() ) {
			log.debug("Forum subscription has been deleted successfully");
		}
    }

    /**
     * @see com.blandware.atleap.service.forum.ForumManager#deleteSubscription(Long, Long, String)
     */
    public void deleteSubscription(Long forumId, Long topicId, String userName)
            throws DeleteException, BeanNotFoundException {
		if ( log.isDebugEnabled() ) {
			log.debug("Deleting forum subscription; forumId=" + forumId +
                    ", topicId=" + topicId + ", userName=" + userName);
		}

        if (forumId == null && topicId == null) {
            String errorMessage = "forumId and topicId cannot be null at the same time";
            throw new DeleteException(errorMessage);
        }

        boolean isForum = forumId != null;

        List subscriptions;
        if (isForum) {
            subscriptions = forumSubscriptionDAO.findForumSubscriptionsByForumIdAndUserName(forumId, userName);
        } else {
            subscriptions = forumSubscriptionDAO.findForumSubscriptionsByTopicIdAndUserName(topicId, userName);
        }

        if (subscriptions == null || subscriptions.isEmpty()) {
            String errorMessage = "No such forum subscriptions exist";
            throw new BeanNotFoundException(errorMessage);
        }

        for (ListIterator i = subscriptions.listIterator(); i.hasNext(); ) {
            ForumSubscription forumSubscription = (ForumSubscription) i.next();
            forumSubscriptionDAO.deleteForumSubscription(forumSubscription);
        }

		if ( log.isDebugEnabled() ) {
			log.debug("Forum subscription has been deleted successfully");
		}
    }

    // ~ Additional methods ================================================================

    /**
     * @see com.blandware.atleap.service.forum.ForumManager#activateSubscription(Long, String)
     */
    public void activateSubscription(Long id, String token)
            throws BeanNotFoundException, UpdateException {
		if ( log.isDebugEnabled() ) {
			log.debug("Activating forum subscription with ID=" + id);
		}

		ForumSubscription forumSubscription = forumSubscriptionDAO.retrieveForumSubscription(id);
		if ( forumSubscription == null ) {
			String errorMessage = "No forum subscription with ID=" + id + "could be found";
			throw new BeanNotFoundException(errorMessage);
		}

        if (!token.equals(forumSubscription.getToken())) {
            String errorMessage = "Forum subscription token does not match one on database; no activation performed";
            throw new UpdateException(errorMessage);
        }

        forumSubscription.setActivated(Boolean.TRUE);
		forumSubscriptionDAO.updateForumSubscription(forumSubscription);

		if ( log.isDebugEnabled() ) {
			log.debug("Forum subscription has been activated successfully");
		}
    }

    /**
     * @see com.blandware.atleap.service.forum.ForumManager#findActivatedSubscriptionsByForumIdOrTopicId(Long, Long)
     */
    public List findActivatedSubscriptionsByForumIdOrTopicId(Long forumId, Long topicId) {
        return forumSubscriptionDAO.findActivatedSubscriptionsByForumIdOrTopicId(forumId, topicId);
    }

    /**
     * @see com.blandware.atleap.service.forum.ForumManager#deleteTimedOutForumSubscriptions()
     */
    public void deleteTimedOutForumSubscriptions() {
		if ( log.isInfoEnabled() ) {
			log.info("Deleting timed out forum subscriptions...");
		}
        List subscriptions = forumSubscriptionDAO.findTimedOutForumSubscriptions();
        int deleted = 0;
        if (subscriptions != null) {
            deleted = subscriptions.size();
            for (Iterator i = subscriptions.iterator(); i.hasNext(); ) {
                ForumSubscription subscription = (ForumSubscription) i.next();
                forumSubscriptionDAO.deleteForumSubscription(subscription);
            }
        }
		if ( log.isInfoEnabled() ) {
			log.info("Deleted " + deleted + " forum subscriptions");
		}
    }

}
